<!DOCTYPE html>
<html lang="en">
<head>
  <title>View Template Rendering - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/view.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/core/view.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>View Template Rendering</h1>
    <p>In most cases, we need to fetch data and render with template files.
So we need to use corresponding view engines.</p>
<p><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> is a built-in plugin to support using multiple view engines in one application.
All view engines are imported as plugins.
With <a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> developers can use the same API interface to work with different view engines.
See <a href="../advanced/view-plugin.html">View Plugin</a> for more details.</p>
<p>Take the officially supported View plugin <a href="https://github.com/eggjs/egg-view-nunjucks" target="_blank" rel="noopener">egg-view-nunjucks</a> as an example:</p>
<h3 id="install-view-engine-plugin"><a class="markdown-anchor" href="#install-view-engine-plugin">#</a> Install view engine plugin</h3>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ npm i egg-view-nunjucks --save</span><br></pre></td></tr></table></figure>
<h3 id="enable-view-engine-plugin"><a class="markdown-anchor" href="#enable-view-engine-plugin">#</a> Enable view engine plugin</h3>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/plugin.js</span></span><br><span class="line">exports.nunjucks = &#123;</span><br><span class="line">  enable: <span class="literal">true</span>,</span><br><span class="line">  package: <span class="string">'egg-view-nunjucks'</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="configure-view-plugins"><a class="markdown-anchor" href="#configure-view-plugins">#</a> Configure view plugins</h2>
<p><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> defines the default configuration of <code>config.view</code></p>
<h3 id="root-string"><a class="markdown-anchor" href="#root-string">#</a> root {String}</h3>
<p>Root directory for template files is absolute path, with default value <code>${baseDir}/app/view</code>.</p>
<p><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> supports having multiple directories, which are separated by <code>,</code>.
In this case, it looks for template files from all the directories.</p>
<p>The configuration below is an example of multiple view directories:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">appInfo</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> config = &#123;&#125;;</span><br><span class="line">  config.view = &#123;</span><br><span class="line">    root: [</span><br><span class="line">      path.join(appInfo.baseDir, <span class="string">'app/view'</span>),</span><br><span class="line">      path.join(appInfo.baseDir, <span class="string">'path/to/another'</span>),</span><br><span class="line">    ].join(<span class="string">','</span>)</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> config;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="cache-boolean"><a class="markdown-anchor" href="#cache-boolean">#</a> cache {Boolean}</h3>
<p>Cache template file paths, default value is <code>true</code>.
<a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> looks for template files from the directories that defined in <code>root</code>.
When a file matching given template path is found, the file's full path will be cached
and reused afterward.
<a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> won't search all directories again for the same template path.</p>
<h3 id="mapping-and-defaultviewengine"><a class="markdown-anchor" href="#mapping-and-defaultviewengine">#</a> mapping and defaultViewEngine</h3>
<p>Every view engine has a view engine name defined when the plugin is enabled.
In view configuration, <code>mapping</code> defines the mapping
from template file's extension name to view engine name.
For example, use Nunjucks engine to render <code>.nj</code> files.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  view: &#123;</span><br><span class="line">    mapping: &#123;</span><br><span class="line">      <span class="string">'.nj'</span>: <span class="string">'nunjucks'</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> uses the corresponding view engine according to the configuration above.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">await</span> ctx.render(<span class="string">'home.nj'</span>);</span><br></pre></td></tr></table></figure>
<p>The mapping from file extension name to view engine must be defined.
Otherwise <a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> cannot find correct view engine.
Global configuration can be done with <code>defaultViewEngine</code>.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  view: &#123;</span><br><span class="line">    defaultViewEngine: <span class="string">'nunjucks'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>If a view engine cannot be found according to specified mapping,
the default view engine will be used.
For the applications that use only one view engine,
it's recommended to set this option.</p>
<h3 id="defaultextension"><a class="markdown-anchor" href="#defaultextension">#</a> defaultExtension</h3>
<p>When calling <code>render()</code>, the first argument should contain file extension name,
unless <code>defaultExtension</code> has been configured.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  view: &#123;</span><br><span class="line">    defaultExtension: <span class="string">'.nj'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// render app/view/home.nj</span></span><br><span class="line"><span class="keyword">await</span> ctx.render(<span class="string">'home'</span>);</span><br></pre></td></tr></table></figure>
<h2 id="render-page"><a class="markdown-anchor" href="#render-page">#</a> Render Page</h2>
<p><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> provides three interfaces in Context.
All three returns a Promise:</p>
<ul>
<li><code>render(name, locals)</code> renders template file, and set the value to <code>ctx.body</code>.</li>
<li><code>renderView(name, locals)</code> renders template file, returns the result and don't set the value to any variable.</li>
<li><code>renderString(tpl, locals)</code> renders template string, returns the result and don't set the value to any variable.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// &#123;app_root&#125;/app/controller/home.js</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HomeController</span> <span class="keyword">extends</span> <span class="title">Controller</span> </span>&#123;</span><br><span class="line">  <span class="keyword">async</span> index() &#123;</span><br><span class="line">    <span class="keyword">const</span> data = &#123; <span class="attr">name</span>: <span class="string">'egg'</span> &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// render a template, path relate to `app/view`</span></span><br><span class="line">    <span class="keyword">await</span> ctx.render(<span class="string">'home/index.tpl'</span>, data);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// or manually set render result to ctx.body</span></span><br><span class="line">    ctx.body = <span class="keyword">await</span> ctx.renderView(<span class="string">'path/to/file.tpl'</span>, data);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// or render string directly</span></span><br><span class="line">    ctx.body = <span class="keyword">await</span> ctx.renderString(<span class="string">'hi, &#123;&#123; name &#125;&#125;'</span>, data, &#123;</span><br><span class="line">      viewEngine: <span class="string">'nunjucks'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>When calling <code>renderString</code>, view engine should be specified unless <code>defaultViewEngine</code> has been defined.</p>
<h2 id="locals"><a class="markdown-anchor" href="#locals">#</a> Locals</h2>
<p>In the process of rendering pages,
we usually need a variable to contain all information that is used in view template.
<a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> provides <code>app.locals</code> and <code>ctx.locals</code>.</p>
<ul>
<li><code>app.locals</code> is global, usually configured in <code>app.js</code>.</li>
<li><code>ctx.locals</code> is per-request, and it merges <code>app.locals</code>.</li>
<li><code>ctx.locals</code> can be assigned by modifying key/value or assigned with a new object. <a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> will merge the new object automatically in corresponding setter.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// `app.locals` merged into `ctx.locals`</span></span><br><span class="line">ctx.app.locals = &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;;</span><br><span class="line">ctx.locals.b = <span class="number">2</span>;</span><br><span class="line"><span class="built_in">console</span>.log(ctx.locals); <span class="comment">// &#123; a: 1, b: 2 &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// in the processing of a request, `app.locals` is merged into `ctx.locals` only at the first time `ctx.locals` being accessed</span></span><br><span class="line">ctx.app.locals = &#123; <span class="attr">a</span>: <span class="number">2</span> &#125;;</span><br><span class="line"><span class="built_in">console</span>.log(ctx.locals); <span class="comment">// already merged before, so output is still &#123; a: 1, b: 2 &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// pass a new object to `locals`. New object will be merged into `locals`, instead of replacing it. It's done by setter automatically.</span></span><br><span class="line">ctx.locals.c = <span class="number">3</span>;</span><br><span class="line">ctx.locals = &#123; <span class="attr">d</span>: <span class="number">4</span> &#125;;</span><br><span class="line"><span class="built_in">console</span>.log(ctx.locals); <span class="comment">// &#123; a: 1, b: 2, c: 3, d: 4 &#125;</span></span><br></pre></td></tr></table></figure>
<p>In practical development, we usually don't use these two objects in controller directly.
Instead, simply call <code>ctx.render(name, data)</code>:</p>
<ul>
<li><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> merges <code>data</code> into <code>ctx.locals</code> automatically.</li>
<li><a href="https://github.com/eggjs/egg-view" target="_blank" rel="noopener">egg-view</a> injects <code>ctx</code>, <code>request</code>, <code>helper</code> into locals automatically.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line">ctx.app.locals = &#123; <span class="attr">appName</span>: <span class="string">'showcase'</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> data = &#123; <span class="attr">name</span>: <span class="string">'egg'</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// will auto merge `data` to `ctx.locals`, output: egg - showcase</span></span><br><span class="line"><span class="keyword">await</span> ctx.renderString(<span class="string">'&#123;&#123; name &#125;&#125; - &#123;&#123; appName &#125;&#125;'</span>, data);</span><br><span class="line"></span><br><span class="line"><span class="comment">// helper, ctx, request will auto inject</span></span><br><span class="line"><span class="keyword">await</span> ctx.renderString(<span class="string">'&#123;&#123; name &#125;&#125; - &#123;&#123; helper.lowercaseFirst(ctx.app.config.baseDir) &#125;&#125;'</span>, data);</span><br></pre></td></tr></table></figure>
<p>Note:</p>
<ul>
<li><strong>ctx.locals is cached. app.locals is merged into it only at the first time that ctx.locals is accessed.</strong></li>
<li>due to the ambiguity of naming, the <code>ctx.state</code> that is used in Koa is replaced by <code>ctx.locals</code> in Egg.js, i.e. <code>ctx.state</code> and <code>ctx.locals</code> are equivalent. It's recommended to use the latter.</li>
</ul>
<h2 id="helper"><a class="markdown-anchor" href="#helper">#</a> Helper</h2>
<p>All functions that defined in <code>helper</code> can be directly used in templates.
See <a href="../basics/extend.html">Extend</a> for more details.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/extend/helper.js</span></span><br><span class="line">exports.lowercaseFirst = <span class="function"><span class="params">str</span> =&gt;</span> str[<span class="number">0</span>].toLowerCase() + str.substring(<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// app/controller/home.js</span></span><br><span class="line"><span class="keyword">await</span> ctx.renderString(<span class="string">'&#123;&#123; helper.lowercaseFirst(name) &#125;&#125;'</span>, data);</span><br></pre></td></tr></table></figure>
<h2 id="security"><a class="markdown-anchor" href="#security">#</a> Security</h2>
<p>The built-in plugin <a href="https://github.com/eggjs/egg-security" target="_blank" rel="noopener">egg-security</a> provides common security helper functions, including <code>helper.shtml / surl / sjs</code> and so on. It's strongly recommended to read <a href="./security.html">Security</a>.</p>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/core/view.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/core/view.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
